<!DOCTYPE html>
<html>

<head>
    <title>Customize your own components</title>
    <script type="text/javascript" src="https://unpkg.com/dat.gui@0.7.6/build/dat.gui.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://unpkg.com/maptalks/dist/maptalks.css">
    <script type="text/javascript" src="https://unpkg.com/maptalks/dist/maptalks.js"></script>
    <script type="text/javascript" src="https://unpkg.com/three@0.138.0/build/three.min.js"></script>
    <script type="text/javascript" src="https://unpkg.com/earcut@2.2.1/dist/earcut.min.js"></script>
    <script type="text/javascript"
        src="https://unpkg.com/three@0.138.0/examples/js/libs/stats.min.js"></script>

    <script type="text/javascript" src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"></script>
    <style>
        html,
        body {
            margin: 0px;
            height: 100%;
            width: 100%;
        }

        #map {
            width: 100%;
            height: 100%;
            background-color: #000;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script>

        var map = new maptalks.Map("map", {
            center: [120.14046159231907, 30.24684576308553],
            zoom: 15,
            pitch: 70,
            // bearing: 180,
            centerCross: true,
            doubleClickZoom: false,
            fog: false,
            baseLayer: new maptalks.TileLayer('tile', {
                urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
                subdomains: ['a', 'b', 'c', 'd'],
                attribution: '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>'
            })
        });

        // the ThreeLayer to draw buildings
        var threeLayer = new maptalks.ThreeLayer('t', {
            forceRenderOnMoving: true,
            forceRenderOnRotating: true,
            // animation: true
        });
        var stats;
        threeLayer.prepareToDraw = function (gl, scene, camera) {
            stats = new Stats();
            stats.domElement.style.zIndex = 100;
            document.getElementById('map').appendChild(stats.domElement);

            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(0, -10, 10).normalize();
            scene.add(light);
            addWater();
        };
        threeLayer.addTo(map);


        var waters;
        function addWater() {
            fetch('./data/westlake.geojson').then(function (res) {
                return res.text();
            }).then(function (geojson) {
                var polygons = maptalks.GeoJSON.toGeometry(geojson);
                waters = polygons.map(p => new CoolWater(p, {}, threeLayer));

                threeLayer.addMesh(waters);

                initGui();
                threeLayer.config('animation', true);
                animation();
            })
        }

        function animation() {
            // layer animation support Skipping frames
            // threeLayer._needsUpdate = !threeLayer._needsUpdate;
            // if (threeLayer._needsUpdate) {
            //     threeLayer.redraw();
            // }
            stats.update();
            requestAnimationFrame(animation);
        }


        function initGui() {
            var params = {
                add: true,
                show: true,
                altitude: 0
            };
            var gui = new dat.GUI();
            gui.add(params, 'add').onChange(function () {
                if (params.add) {
                    threeLayer.addMesh(waters);
                } else {
                    threeLayer.removeMesh(waters);
                }
            });
            gui.add(params, 'show').onChange(function () {
                waters.forEach(function (mesh) {
                    if (params.show) {
                        mesh.show();
                    } else {
                        mesh.hide();
                    }
                });
            });
            gui.add(params, 'altitude', 0, 300).onChange(function () {
                waters.forEach(function (mesh) {
                    mesh.setAltitude(params.altitude);
                });
            });
        }



        /**
         * https://www.shadertoy.com/view/llc3Dn
         */
        const fragmentShader = `
uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds)
uniform sampler2D iChannel0; 
uniform sampler2D iChannel1; 

const vec3 waterColor = vec3(.1, .6, .9);

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy / iResolution.xy;
    vec2 growingUv = sin((uv + iTime / 20.) * 3.);
    vec2 uv2 = texture2D(iChannel0, growingUv).rr;

    fragColor = vec4((1. - texture2D(iChannel1, uv * 0.95 + uv2 * 0.05).rgb * (1. - waterColor)), 1.0);
}
void main(){
    mainImage(gl_FragColor,gl_FragCoord.xy);
}
`;
        //default values
        var OPTIONS = {
            interactive: false,
            altitude: 0
        };

        class CoolWater extends maptalks.BaseObject {
            constructor(polygon, options, layer) {
                options = maptalks.Util.extend({}, OPTIONS, options, { layer, polygon });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);

                let textureLoader = new THREE.TextureLoader();
                let iChannel0 = textureLoader.load('data/CoolWater-iChannel0.png');
                iChannel0.wrapS = iChannel0.wrapT = THREE.RepeatWrapping;
                let iChannel1 = textureLoader.load('data/CoolWater-iChannel1.jpg');
                iChannel1.wrapS = iChannel1.wrapT = THREE.RepeatWrapping;

                let material = this.material = new THREE.ShaderMaterial({
                    fragmentShader,
                    uniforms: {
                        iTime: {
                            type: 'f',
                            value: 0
                        },
                        iResolution: {
                            type: 'v3',
                            value: new THREE.Vector3(1, 1, 1)
                        },
                        iChannel0: {
                            type: 't',
                            value: iChannel0
                        },
                        iChannel1: {
                            type: 't',
                            value: iChannel1
                        }
                    }
                });
                let size = layer.getMap().getSize();
                material.uniforms.iResolution.value.set(size.width, size.height, 1);
                const geometry = getWaterGeometry(polygon, layer);
                this._createMesh(geometry, material);

                //set object3d position
                const { altitude } = options;
                const z = layer.altitudeToVector3(altitude, altitude).x;
                const center = polygon.getCenter();
                const v = layer.coordinateToVector3(center, z);
                this.getObject3d().position.copy(v);

            }

            _animation() {
                this.material.uniforms.iTime.value += 1.0 / 60.0;
            }
        }



        function getSingleGeometry(polygon, layer, centerPt) {
            //if lnglats
            if (Array.isArray(polygon)) {
                polygon = new maptalks.Polygon(polygon);
            }

            const shell = polygon.getShell();
            const holes = polygon.getHoles();
            const lnglats = shell;
            const positions = [],
                holesIndices = [], positionsV = [];
            lnglats.forEach(lnglat => {
                const v = layer.coordinateToVector3(lnglat).sub(centerPt);
                positions.push(v.x, v.y, v.z);
                positionsV.push(v);
            });
            if (holes && holes.length > 0) {
                holes.forEach(hole => {
                    holesIndices.push(positionsV.length);
                    hole.forEach(lnglat => {
                        const v = layer.coordinateToVector3(lnglat).sub(centerPt);
                        positions.push(v.x, v.y, v.z);
                        positionsV.push(v);
                    });
                });
            }
            const hole = (holesIndices.length > 0 ? holesIndices : undefined);
            const faces = earcut(positions, hole, 3);
            return {
                positionsV,
                faces
            };
        }

        function getWaterGeometry(polygon, layer) {
            const geometry = new THREE.Geometry();
            let positions = [],
                fcs = [];
            const centerPt = layer.coordinateToVector3(polygon.getCenter());
            if (polygon instanceof maptalks.Polygon) {
                const {
                    positionsV,
                    faces
                } = getSingleGeometry(polygon, layer, centerPt);
                positions = positionsV;
                fcs = faces;
            } else {
                const lnglats = polygon.getCoordinates();
                for (let i = 0, len = lnglats.length; i < len; i++) {
                    const {
                        positionsV,
                        faces
                    } = getSingleGeometry(lnglats[i], layer, centerPt);
                    if (i > 0) {
                        const LEN = positions.length;
                        for (let i = 0, len = faces.length; i < len; i++) {
                            faces[i] += LEN;
                        }
                    }
                    positionsV.forEach(p => {
                        positions.push(p);
                    });
                    faces.forEach(f => {
                        fcs.push(f);
                    });
                }
            }

            geometry.vertices = positions;
            for (let i = 0, len = fcs.length; i < len; i += 3) {
                const face = new THREE.Face3(
                    fcs[i],
                    fcs[i + 1],
                    fcs[i + 2]
                );
                geometry.faces.push(face);
            }
            // geometry.computeFlatVertexNormals();
            // geometry.computeMorphNormals();
            geometry.computeVertexNormals();
            geometry.computeFaceNormals();
            return geometry;
        }
    </script>
</body>

</html>